﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;

namespace CMS6Tools.Common.Email
{
    public class EmailService 
    { 
        static readonly object lockObj = new object();
        bool _initialized = false;

        protected string SmtpCredentialPassword { get; set; }
        protected string SmtpHost { get; set; }
        protected int SmtpPort { get; set; }
        protected bool SmtpEnableSsl { get; set; }
        protected string SmtpCredentialUserName { get; set; }
        protected string DefaultFromEmail { get; set; }
        protected string DefaultFromName { get; set; }

        protected void Initialize()
        {
            if (!_initialized)
            {
                lock (lockObj)
                {
                    if (!_initialized)
                    {
                        //email system variables
                        SmtpHost = ConfigurationManager.AppSettings[Constants.EmailSmtpHostKey];
                        SmtpPort = Int32.Parse(ConfigurationManager.AppSettings[Constants.EmailSmtpPortKey]);
                        SmtpEnableSsl = Boolean.Parse(ConfigurationManager.AppSettings[Constants.EmailSmtpEnableSslKey]);
                        SmtpCredentialUserName = ConfigurationManager.AppSettings[Constants.EmailSmtpCredentialUserNameKey];
                        DefaultFromEmail = ConfigurationManager.AppSettings[Constants.EmailDefaultFromEmailKey];
                        DefaultFromName = ConfigurationManager.AppSettings[Constants.EmailDefaultFromNameKey];
                        
                        _initialized = true;
                    }
                }
            }
        }
         

        #region public method
        public bool SendEmail(string[] emailTo, string[] cc, string[] bcc, EmailTemplate template, ITextTemplateExtraData extraData, bool aSyncSend = true, SendCompletedEventHandler aSyncSendCompletedEventHandler = null, string plainViewContent = null, Attachment[] attachments = null)
        {
            if (emailTo == null || !emailTo.Any())
                return false;

            var toList = emailTo.Select(e => new MailAddress(e)).ToArray();
            var ccList = cc == null ? default(MailAddress[]) : cc.Select(e => new MailAddress(e)).ToArray();
            var bccList = bcc == null ? default(MailAddress[]) : bcc.Select(e => new MailAddress(e)).ToArray();
            return SendEmail(toList, template, extraData, null, ccList, bccList, aSyncSend: aSyncSend, aSyncSendCompletedEventHandler: aSyncSendCompletedEventHandler, plainViewContent: plainViewContent, attachments: attachments);
        }

        public bool SendEmail(MailAddress[] to, EmailTemplate template, ITextTemplateExtraData extraData, MailAddress from = null, MailAddress[] cc = null, MailAddress[] bcc = null, MailAddress[] replyToList = null, MailAddress sender = null, bool isBodyHtml = true, bool addCarriageReturn = true, bool aSyncSend = true, SendCompletedEventHandler aSyncSendCompletedEventHandler = null, string plainViewContent = null, Attachment[] attachments = null)
        {
            if (template == null)
                throw new ArgumentNullException("template");

            if (to == null || !to.Any())
                return false;

            bool isActionSuccess = false;
            string content = string.Empty;
            string subject = string.Empty;
            string body = string.Empty;

            try
            {
                content = template.Transform(extraData);
                template.GetSubjectAndBody(content, out subject, out body);
                isActionSuccess = true;
            }
            catch (Exception ex)
            {
                isActionSuccess = false;
            }

            if (isActionSuccess)
                isActionSuccess = SendEmail(to, subject, body, from, cc, bcc, replyToList, sender, isBodyHtml, addCarriageReturn, aSyncSend, aSyncSendCompletedEventHandler, plainViewContent: plainViewContent, attachments: attachments);

            return isActionSuccess;
        }

        public bool SendEmail(MailAddress[] to, string subject, string body, MailAddress from = null, MailAddress[] cc = null, MailAddress[] bcc = null, MailAddress[] replyToList = null, MailAddress sender = null, bool isBodyHtml = true, bool addCarriageReturn = true, bool aSyncSend = true, SendCompletedEventHandler aSyncSendCompletedEventHandler = null, string plainViewContent = null, Attachment[] attachments = null, IDisposable[] extraState = null)
        {
            if (to == null || !to.Any())
                return false;

            bool isActionSuccess = false;
            try
            {
                Initialize();

                from = from ?? new MailAddress(DefaultFromEmail, DefaultFromName);
                cc = cc ?? new MailAddress[0];
                bcc = bcc ?? new MailAddress[0];
                attachments = attachments ?? new Attachment[0];
                replyToList = replyToList ?? new MailAddress[0];

                if (string.IsNullOrEmpty(subject) || string.IsNullOrEmpty(body))
                {
                    return false;
                }

                subject = System.Net.WebUtility.HtmlDecode(subject);

                //build mail message
                MailMessage msg = new MailMessage();
                msg.From = from;
                msg.Sender = sender;

                if (replyToList != null && replyToList.Any())
                {
                    replyToList = GetDistinctMailAddress(replyToList);
                    foreach (var temp in replyToList)
                    {
                        if (temp != null)
                            msg.ReplyToList.Add(temp);
                    }
                }
                to = GetDistinctMailAddress(to);
                foreach (var temp in to)
                {
                    if (temp != null)
                        msg.To.Add(temp);
                }
                if (cc != null && cc.Any())
                {
                    cc = GetDistinctMailAddress(cc);
                    foreach (var temp in cc)
                    {
                        if (temp != null)
                            msg.CC.Add(temp);
                    }
                }
                if (bcc != null && bcc.Any())
                {
                    bcc = GetDistinctMailAddress(bcc);
                    foreach (var temp in bcc)
                    {
                        if (temp != null)
                            msg.Bcc.Add(temp);
                    }
                }

                subject = subject.Replace("\n", "");
                subject = subject.Replace("\r", "");
                subject = subject.Trim();
                msg.Subject = subject;
                msg.SubjectEncoding = Encoding.UTF8;

                if (addCarriageReturn)
                    body = AddCarriageReturn(body, 900);

                if (string.IsNullOrWhiteSpace(plainViewContent))
                {
                    msg.Body = body;
                    msg.BodyEncoding = Encoding.UTF8;
                    msg.IsBodyHtml = isBodyHtml;
                }
                else
                {
                    var plainView = AlternateView.CreateAlternateViewFromString(plainViewContent, Encoding.UTF8, "text/plain");
                    var htmlView = AlternateView.CreateAlternateViewFromString(body, Encoding.UTF8, "text/html");
                    msg.AlternateViews.Add(plainView);
                    msg.AlternateViews.Add(htmlView);
                }

                msg.Priority = MailPriority.Normal;

                if (attachments != null && attachments.Any())
                {
                    foreach (var attachment in attachments)
                    {
                        msg.Attachments.Add(attachment);
                    }
                }

                //send mail message
                SmtpClient client = new SmtpClient(SmtpHost);
                client.EnableSsl = SmtpEnableSsl;
                client.Port = SmtpPort;
                if (string.IsNullOrEmpty(SmtpCredentialUserName))
                {
                    client.UseDefaultCredentials = true;
                }
                else
                {
                    client.Credentials = new NetworkCredential(SmtpCredentialUserName, SmtpCredentialPassword);
                }

                if (!aSyncSend)
                {
                    client.Send(msg);
                    isActionSuccess = true;
                }
                else
                {
                    client.SendCompleted += new SendCompletedEventHandler(aSyncSendCompletedEventHandler ?? DefaultSendCompletedEventHandler);
                    client.SendAsync(msg, new
                    {
                        from = from.Address,
                        to = to.Select(d => d.Address).ToArray(),
                        cc = cc.Select(c => c.Address).ToArray(),
                        subject = subject,
                        aSyncSend = aSyncSend,
                        // Can't add Message and Client here, as it will increase CPU.
                    });
                    isActionSuccess = true;
                }
            }
            catch (Exception ex)
            {
                isActionSuccess = false;
            }

            return isActionSuccess;
        }
        #endregion

        #region assist method
        private string AddCarriageReturn(string input, int interval)
        {
            var output = new StringBuilder();
            while (input.Length > interval)
            {
                string rawSubString = input.Substring(0, interval);
                int lastIndexOfSpace = 0;
                lastIndexOfSpace = rawSubString.LastIndexOf(" ");
                if (lastIndexOfSpace > 0)
                {
                    output.Append(rawSubString.Substring(0, lastIndexOfSpace));
                    output.AppendLine();
                    input = input.Substring(lastIndexOfSpace);
                }
                else
                {
                    output.Append(rawSubString);
                    output.AppendLine();
                    input = input.Substring(interval);
                }
            }
            output.Append(input.Substring(0));
            return output.ToString();
        }

        private void DefaultSendCompletedEventHandler(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            using (var streamWriter = new StreamWriter("D:\\a.txt"))
            {
                streamWriter.Write(string.Format("{0} - {1}", DateTime.Now.ToString(), e.Error == null));
            }
            if (e.Cancelled)
            {
                // canceled
            }
            if (e.Error != null)
            {
                dynamic state = e.UserState;
                if (state != null)
                {
                }
                // error occurred 
            }
            else
            {
                dynamic state = e.UserState;
                if (state != null)
                {
                }
                // sent successfully
            }

        }

        // remove duplicate Emails
        private MailAddress[] GetDistinctMailAddress(MailAddress[] mails)
        {
            List<MailAddress> newEmails = new List<MailAddress>();
            if (mails != null && mails.Count() > 0)
            {
                foreach (var mail in mails)
                {
                    if (!newEmails.Any(e => e.Address.Equals(mail.Address, StringComparison.InvariantCultureIgnoreCase)))
                        newEmails.Add(mail);
                }
            }

            return newEmails.ToArray();
        }

        private string FormatEmailBeginSendInfo(string from, string[] emailTo, string[] cc, string subject, bool async)
        {
            return string.Format("{4} Email:{3} begin sending from {0} to {1}, cc to {2}.", from, string.Join(",", emailTo), string.Join(",", cc), subject, async ? "Async" : "Sync");
        }
        private string FormatEmailSendFailedException(string from, string[] emailTo, string[] cc, string subject, bool async)
        {
            return string.Format("{4} Email:{3} was sent failed, from {0} to {1}, cc to {2}.", from, string.Join(",", emailTo), string.Join(",", cc), subject, async ? "Async" : "Sync");
        }
        private string FormatEmailSendSuccessInfo(string from, string[] emailTo, string[] cc, string subject, bool async)
        {
            return string.Format("{4} Email:{3} has been sent from {0} to {1}, cc to {2} successful.", from, string.Join(",", emailTo), string.Join(",", cc), subject, async ? "Async" : "Sync");
        }
        #endregion
    }
}
